#include "handler/transfile.h"
#include "logger.h"
#include "network.h"
#include "db/db.h"
#include "db/file.h"
#include "get_hash.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/sendfile.h>
#include <sys/mman.h>
#include <fcntl.h>

void clientUploadFile(void *arg)
{
    // 这里要建立一个临时变量存储好客户端的当前状态，否则用户可能切换工作目录
    Client_t client = *(Client_t *)arg;

    FileInfo_t file_info;
    bzero(&file_info, sizeof(FileInfo_t));
    ssize_t bytes_received;

    bytes_received = recv(client.tunnel_fd, &file_info, sizeof(FileInfo_t), MSG_WAITALL);
    // printf("@@@@@@file_path: %s   @@@@@\n", file_info.file_name);
    if (bytes_received != sizeof(FileInfo_t))
    {
        PRINT_ERROR("recv file_info");
        return;
    }
    LOG_INFO("recvFile", "Client-%d uploading file...", client.tunnel_fd);
    LOG_INFO("recvFile", "FileName: %s FileSize: %lu",
             file_info.file_name, file_info.file_size);

    // 查询文件表里是否有该实际文件
    char sql_select[512] = {0};
    MYSQL_RES *result = NULL;
    MYSQL_ROW row;
    MYSQL *conn = threadConnectDB();

    // 获取用户id
    sprintf(sql_select, "select user_id from users where username = '%s'", client.user_info.name);
    result = excutesql(sql_select, conn);
    row = mysql_fetch_row(result);
    int user_id = atoi(row[0]);
    mysql_free_result(result);

    sprintf(sql_select, "select user_id, file_name, file_path, hash, is_del from files where hash = '%s'", file_info.md5);
    result = excutesql(sql_select, conn);
    row = mysql_fetch_row(result);
    if (row == NULL)
    {
        // 发一个file_info结构体标记偏移值
        send(client.tunnel_fd, &file_info, sizeof(FileInfo_t), 0);
        recvFile(&file_info, client.tunnel_fd);
        insertfiles(&client, conn, file_info.file_name, 2, file_info.file_size, file_info.md5);
    }
    else
    {
        int flag[4] = {0};
        char file_path[512] = {0};
        sprintf(file_path, "%s%s", client.cur_path, file_info.file_name);
        while (row != NULL)
        {
            if (atoi(row[0]) == user_id && atoi(row[4]) == 0 && strcmp(row[2], file_path) == 0)
            {
                char MD5[33] = {0};
                getHashMD5(row[3], MD5);
                if (strcmp(row[3], MD5) == 0)
                {
                    flag[0] = 1;
                }
                else
                {
                    flag[1] = 1;
                }
            }
            if (atoi(row[0]) == user_id && atoi(row[4]) == 1 && strcmp(row[2], file_path) == 0)
            {
                flag[2] = 1;
            }
            if (atoi(row[0]) == user_id && strcmp(row[2], file_path) != 0)
            {
                flag[3] = 1;
            }
            row = mysql_fetch_row(result);
        }
        mysql_free_result(result);

        struct stat fileStat;
        stat(file_info.md5, &fileStat);

        // 有该文件且已经传输完
        if (flag[0] == 1)
        {

            file_info.offset = fileStat.st_size;
            send(client.tunnel_fd, &file_info, sizeof(FileInfo_t), 0);
        }
        // 有该文件但未传输完
        else if (flag[1] == 1)
        {
            LOG_INFO("transfile", "路径相同但未传输完");
            struct stat fileStat;
            stat(file_info.md5, &fileStat);
            file_info.offset = fileStat.st_size;
            send(client.tunnel_fd, &file_info, sizeof(FileInfo_t), 0);
            recvFile(&file_info, client.tunnel_fd);
            char sql_update[1024] = {0};
            sprintf(sql_update, "update files set file_size = %ld where user_id = %d and file_path = '%s'", file_info.file_size, user_id, file_path);
            puts(sql_update);
            excutesql(sql_update, conn);
        }
        // 路径相同但已经被删除
        else if (flag[2] == 1)
        {
            LOG_INFO("transfile", "路径相同但已经被删除！");
            file_info.offset = fileStat.st_size;
            send(client.tunnel_fd, &file_info, sizeof(FileInfo_t), 0);
            char sql_update[1024] = {0};
            sprintf(sql_update, "update files set is_del = 0 where user_id = %d and file_path = '%s'", user_id, file_path);
            excutesql(sql_update, conn);
        }
        // 路径不同但有源文件！
        else if (flag[3] == 1)
        {
            file_info.offset = fileStat.st_size;
            send(client.tunnel_fd, &file_info, sizeof(FileInfo_t), 0);
            LOG_INFO("transfile", "路径不同但有源文件！");
            puts(file_info.file_name);
            insertfiles(&client, conn, file_info.file_name, 2, file_info.file_size, file_info.md5);
        }
        else
        {
            file_info.offset = fileStat.st_size;
            send(client.tunnel_fd, &file_info, sizeof(FileInfo_t), 0);
            insertfiles(&client, conn, file_info.file_name, 2, file_info.file_size, file_info.md5);
        }
    }

    closeDB(conn);
    return;
}

void clientDownloadFile(void *arg)
{
    Client_t client = *(Client_t *)arg;
    FileInfo_t fileinfo;
    recv(client.tunnel_fd, &fileinfo, sizeof(fileinfo), MSG_WAITALL);

    // 查询文件表里是否有该实际文件
    char sql_select[1024] = {0};
    MYSQL *conn = threadConnectDB();
    MYSQL_RES *result = NULL;
    MYSQL_ROW row;

    // 获取用户id
    sprintf(sql_select, "select user_id from users where username = '%s'", client.user_info.name);
    result = excutesql(sql_select, conn);
    row = mysql_fetch_row(result);
    int user_id = atoi(row[0]);
    mysql_free_result(result);

    char file_path[512] = {0};
    sprintf(file_path, "%s%s", client.cur_path, fileinfo.file_name);
    sprintf(sql_select, "select file_id, file_size, hash from files where file_path = '%s' and user_id = %d and is_del = 0", file_path, user_id);
    result = excutesql(sql_select, conn);
    row = mysql_fetch_row(result);
    if (row == NULL)
    {
        // 没有该文件，下载信息错误
        send(client.tunnel_fd, &fileinfo, sizeof(fileinfo), 0);
        return;
    }
    else
    {
        memcpy(fileinfo.md5, row[2], sizeof(fileinfo.md5));
        long file_size = atol(row[1]);
        fileinfo.file_size = file_size;

        printf("file_size: %ld\n", fileinfo.file_size);

        send(client.tunnel_fd, &fileinfo, sizeof(FileInfo_t), 0);
        if (file_size != fileinfo.offset)
        {
            sendFile(&fileinfo, client.tunnel_fd);
        }
    }
}

void recvFile(FileInfo_t *file_info, int tunnel_fd)
{
    ssize_t bytes_received, bytes_written;
    int file_fd = open(file_info->md5, O_RDWR | O_CREAT | O_APPEND, 0666);

    char buffer[BUFFER_SIZE];
    long remaining_bytes = file_info->file_size - file_info->offset;
    while (remaining_bytes > 0)
    {
        bytes_received = recv(tunnel_fd, buffer, BUFFER_SIZE, 0);
        if (bytes_received <= 0)
        {
            file_info->file_size = file_info->file_size - remaining_bytes;
            PRINT_ERROR("recv file buffer");
            close(file_fd);
            return;
        }

        bytes_written = write(file_fd, buffer, bytes_received);
        if (bytes_written != bytes_received)
        {
            PRINT_ERROR("write file");
            close(file_fd);
            return;
        }

        remaining_bytes -= bytes_written;
    }

    close(file_fd);
    return;
}

void recvFile_mmap(FileInfo_t *file_info, int tunnel_fd)
{
    ssize_t bytes_received;
    int file_fd = open(file_info->md5, O_RDWR | O_CREAT | O_TRUNC, 0666);

    char buffer[8192] = {0};
    long offset = file_info->offset;
    long remaining_bytes = file_info->file_size - offset;

    LOG_DEBUG("transfile", "before: remaining: %ld", remaining_bytes);

    while (remaining_bytes > 0)
    {
        if (remaining_bytes < 8192)
        {
            memset(buffer, 0, sizeof(buffer));
            bytes_received = recv(tunnel_fd, buffer, remaining_bytes, MSG_WAITALL);
        }
        else
        {
            memset(buffer, 0, sizeof(buffer));
            bytes_received = recv(tunnel_fd, buffer, 8192, MSG_WAITALL);
        }

        if (bytes_received <= 0)
        {
            LOG_DEBUG("transfile", "end: remaining: %ld", remaining_bytes);
            file_info->file_size = file_info->file_size - remaining_bytes;
            PRINT_ERROR("recv file buffer");
            close(file_fd);
            return;
        }
        int ret = ftruncate(file_fd, offset + bytes_received);
        if (ret == -1)
        {
            perror("ftruncate");
        }
        // printf("rec: %ld, file_fd: %d, offset: %ld", bytes_received, file_fd, offset);
        char *p = (char *)mmap(NULL, bytes_received, PROT_READ | PROT_WRITE, MAP_SHARED, file_fd, offset);
        if (p == MAP_FAILED)
        {
            perror("mmap");
        }
        remaining_bytes = remaining_bytes - bytes_received;
        memcpy(p, buffer, bytes_received);
        offset += bytes_received;
        munmap(p, bytes_received);
    }

    close(file_fd);
    return;
}

void sendFile(FileInfo_t *file_info, int tunnel_fd)
{
    long bytes_sent = 0;
    int file_fd;

    file_fd = open(file_info->md5, O_RDONLY);
    if (file_fd == -1)
    {
        PRINT_ERROR("open file");
        file_info->is_complete = false;
        return;
    }

    struct stat stat_file;
    fstat(file_fd, &stat_file);
    long size = stat_file.st_size - file_info->offset;
    long offset = file_info->offset;

    PRINT_DEBUG("sendfile start at: %ld", offset);
    while (size > 0)
    {
        bytes_sent = sendfile(tunnel_fd, file_fd, &offset, size);
        if (bytes_sent == -1)
        {
            break;
        }
        size = size - bytes_sent;
        // offset = offset + bytes_sent; sendfile已经对offset做了修改
    }

    if (size > 0)
    {

        PRINT_INFO("文件传输未完成！");
        LOG_INFO("sendfile", "文件传输未完成");
        return;
    }

    LOG_INFO("sendFile", "%s sent to client!", file_info->file_name);
    PRINT_INFO("%s downloaded!", file_info->file_name);
    file_info->is_complete = true;
    close(file_fd);
}
